package com.ambition.mallchat.common.websocket.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.ambition.mallchat.common.user.dao.UserDao;
import com.ambition.mallchat.common.user.domain.entity.User;
import com.ambition.mallchat.common.user.service.LoginService;
import com.ambition.mallchat.common.websocket.domain.enums.WSRespTypeEnum;
import com.ambition.mallchat.common.websocket.domain.vo.resp.WSBaseResp;
import com.ambition.mallchat.common.websocket.domain.vo.resp.WSLoginUrl;
import com.ambition.mallchat.common.websocket.dto.WSChannelExtraDTO;
import com.ambition.mallchat.common.websocket.service.WebsocketService;
import com.ambition.mallchat.common.websocket.service.adapter.WebsocketAdapter;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.netty.channel.Channel;

import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: mallchat-learnning
 * @ClassName WebsocketServiceImpl
 * @description: websocket主要业务实现
 * @author: oldTea
 * @create: 2023-10-26 23:06
 * @Version 1.0
 **/
@Service
public class WebsocketServiceImpl implements WebsocketService {

    @Autowired
    WxMpService wxMpService;

    @Autowired
    UserDao userDao;

    @Autowired
    private LoginService loginService;
    /**
     * 用来管理用户的连接，包括游客和登录态
     */
    private static final ConcurrentHashMap<Channel,WSChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();

    public static final int MAXIMUM_SIZE = 1000;
    public static final Duration DURATION = Duration.ofHours(1);

    /**
     * 用来保存登录的通道和状态码
     * @param channel
     */
    private static final Cache<Integer,Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder()
            .maximumSize(MAXIMUM_SIZE)
            .expireAfterWrite(DURATION)
            .build();


    @Override
    public void connect(Channel channel) {
        ONLINE_WS_MAP.put(channel,new WSChannelExtraDTO());
    }

    @SneakyThrows
    @Override
    public void handeleLoginReq(Channel channel) {
        //生成随机code
        Integer code = generateLoginCode(channel);
        //找微信生成带code参数的二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());
        //返回给前端
        sendMsg(channel,WebsocketAdapter.buildResp(wxMpQrCodeTicket));
    }

    @Override
    public void remove(Channel channel) {
        ONLINE_WS_MAP.remove(channel);
        //todo 用户下线

    }

    @Override
    public Channel getChannelByCode(Integer code) {
        Channel ifPresent = WAIT_LOGIN_MAP.getIfPresent(code);
        return ifPresent;
    }

    @Override
    public void scanLoginSuccess(Integer code, Long id) {
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if(ObjectUtil.isNull(channel)){
            return;
        }
        User user = userDao.getById(id);
        //移除code
        WAIT_LOGIN_MAP.invalidate(code);
        //调用登录模块获取token
        String token = loginService.login(user.getId());

        sendMsg(channel,WebsocketAdapter.buildResp(user,token));

    }

    @Override
    public void waitAuthorize(Integer code) {
        Channel channel= WAIT_LOGIN_MAP.getIfPresent(code);
        sendMsg(channel,WebsocketAdapter.buildWaitAuthorize());

    }

    private void sendMsg(Channel channel, WSBaseResp<?> wsBaseResp) {
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(wsBaseResp)));
    }

    /**
     *
     putIfAbsent 当map里有当前值时会返回当前值，没有时插入成功返回Null
     */
    private Integer generateLoginCode(Channel channel) {
        Integer code;
        do {
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
        }while (ObjectUtil.isNotNull(WAIT_LOGIN_MAP.asMap().putIfAbsent(code,channel)));
        return code;
    }
}